﻿using System;
using System.Data;
using System.IO;
using System.Windows.Forms;
using HtmlAgilityPack;
using Microsoft.Ajax.Utilities;
using System.ComponentModel;
using System.Threading;
using System.Reflection;

namespace JSLWinform
{
    public partial class MainForm : Form
    {   // Test
        String inputFilePath;
        public static StreamWriter infoWriter;
        public static BackgroundWorker workerJSParser;
        public static bool isCancelled;
        public static bool isParserError;
        public static bool noScriptFound;
        public static int jsFunctionCount;
        public static string parserErrorText;
        public static Int32 scriptStartLineNumber;

        public static DataTable dtblFunctionInfos;
        public static DataGridView gridviewError;

        public MainForm()
        {            
            InitializeComponent();
            inputFilePath = Application.StartupPath + "\\test\\jquery-1.9.1.js";
            textBoxFilePath.Text = inputFilePath;
            openFileDialog.InitialDirectory = Application.StartupPath + "\\test\\";
            radioButtonWholeFile.Checked = true;
            gridviewFunctionInfos.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
            gridviewFunctionInfos.RowHeadersVisible = false;
            gridviewFunctionInfos.Columns["FuncName"].FillWeight = 23;
            gridviewFunctionInfos.Columns["NameContext"].FillWeight = 23;
            gridviewFunctionInfos.Columns["NameGuess"].FillWeight = 23;
            gridviewFunctionInfos.Columns["NamePossible"].FillWeight = 23;
            gridviewFunctionInfos.Columns["Row"].FillWeight = 8;
            buttonCancel.Enabled = false;
        }

        private void buttonOpenFileDialog_Click(object sender, EventArgs e)
        {
            openFileDialog.ShowDialog();
        }

        private void openFileDialog_FileOk(object sender, CancelEventArgs e)
        {
            textBoxFilePath.Text = openFileDialog.FileName;
        }

        private void buttonCancel_Click(object sender, EventArgs e)
        {
            if (workerJSParser.IsBusy)
            {
                workerJSParser.CancelAsync();
            }
        }

        private void InitializeDtblFunctionInfos()
        {
            dtblFunctionInfos = new DataTable();
            dtblFunctionInfos.Columns.Add("Row", typeof(Int32));
            dtblFunctionInfos.Columns.Add("Name", typeof(String));
            dtblFunctionInfos.Columns.Add("NameContext", typeof(String));
            dtblFunctionInfos.Columns.Add("NameGuess", typeof(String));
            dtblFunctionInfos.Columns.Add("NamePossible", typeof(String));
        }

        private void InitializeInterface()
        {
            textBoxStatus.Text = "Locating...";
            buttonParse.Enabled = false;
            buttonCancel.Enabled = true;
            gridviewFunctionInfos.DataSource = new DataTable();
            if (tabControlResults.TabPages.ContainsKey("tabPageError"))
            {
                tabControlResults.TabPages.RemoveByKey("tabPageError");
            }
            if (gridviewError != null)
            {
                gridviewError.Dispose();
            }
        }

        private void InitializeVariables()
        {
            isCancelled = false;
            noScriptFound = false;
            jsFunctionCount = 0;
            isParserError = false;
            parserErrorText = "";
            scriptStartLineNumber = 0;
        }

        private void InitializeWorkerJSParser()
        {
            workerJSParser = new BackgroundWorker();
            workerJSParser.WorkerSupportsCancellation = true;
            workerJSParser.WorkerReportsProgress = true;
            workerJSParser.DoWork += new DoWorkEventHandler(workerJSParser_DoWork);
            workerJSParser.ProgressChanged += new ProgressChangedEventHandler(workerJSParser_ProgressChanged);
            workerJSParser.RunWorkerCompleted += new RunWorkerCompletedEventHandler(workerJSParser_RunWorkerCompleted);
            workerJSParser.RunWorkerAsync();
        }

        private void buttonParse_Click(object sender, EventArgs e)
        {
            inputFilePath = textBoxFilePath.Text;
            if (File.Exists(inputFilePath))
            {
                InitializeDtblFunctionInfos();
                InitializeInterface();
                InitializeVariables();
                InitializeWorkerJSParser();
            }
            else
            {
                textBoxStatus.Text = "File does not exist";
            }
        }

        private bool isjQueryExpression(AstNode node)
        {
            bool result = false;

            if (node.GetType() == typeof(CallNode))
            {
                CallNode callNode = node as CallNode;

                if (callNode.LeftHandSide.GetType() == typeof(Lookup))
                {
                    Lookup lookup = callNode.LeftHandSide as Lookup;

                    if (lookup.Name == "$" || lookup.Name == "jQuery")
                    {
                        result = true;
                    }
                }
            }
            
            return result;
        }

        bool isDOMReadyFunction(Lookup node)
        {
            bool result = false;

            if (node.Name == "$" || node.Name == "jQuery")
            {
                result = true;
            }

            return result;
        }

        private string findFunctionName(FunctionObject funcObj)
        {
            String possibleName = "";

            if (isjQueryExpression(funcObj.Parent.Parent))
            {
                CallNode jQueryExpression = funcObj.Parent.Parent as CallNode;

                if (jQueryExpression.Function.GetType() == typeof(Lookup))
                {
                    if (isDOMReadyFunction(jQueryExpression.Function as Lookup))
                    {
                        possibleName = "DOM ready function";
                    }
                }
                else if (jQueryExpression.Function.GetType() == typeof(Member))
                {
                    ; ;
                }
                else
                {
                    ; ; // Unhandle
                }
            }
            else
            {
                ; ; // Unhandled case
            }

            return possibleName;
        }

        void HandleAstNode(FunctionObject node)
        {
            jsFunctionCount++;
            workerJSParser.ReportProgress(jsFunctionCount);
            FunctionObject funcObj = node as FunctionObject;
            String funcName = (funcObj.Name != null) ? funcObj.Name : "";
            String funcNameContext = (funcObj.NameContext != null && funcObj.NameContext.Code != null) ? funcObj.NameContext.Code : "";
            DataRow dr = dtblFunctionInfos.NewRow();
            dr["Name"] = funcName;
            dr["NameContext"] = funcNameContext;
            dr["NameGuess"] = funcObj.NameGuess;
            dr["NamePossible"] = (funcName == "" && funcNameContext == "" && funcObj.NameGuess == "") ? findFunctionName(funcObj) : "";
            dr["Row"] = funcObj.Context.StartLineNumber + scriptStartLineNumber - 1;
            dtblFunctionInfos.Rows.Add(dr);
        }

        private string findFunctionName(Lookup func)
        {
            String possibleName = "";

            if (func.Parent.GetType() == typeof(CallNode))
            {
                if (func.Parent.Parent.GetType() == typeof(VariableDeclaration))
                {
                    VariableDeclaration varDecl = func.Parent.Parent as VariableDeclaration;
                    possibleName = varDecl.Name;
                }
                else
                {
                    ; ; // Unhandle
                }
            }
            else
            {
                ; ; // Unhandle
            }

            return possibleName;
        }

        void HandleAstNode(Lookup node)
        {
            if (node.Name == "Function")
            {
                DataRow dr = dtblFunctionInfos.NewRow();
                dr["Name"] = "";
                dr["NameContext"] = "";
                dr["NameGuess"] = "";
                dr["NamePossible"] = findFunctionName(node);
                dr["Row"] = node.Context.StartLineNumber + scriptStartLineNumber - 1;
                dtblFunctionInfos.Rows.Add(dr);
            }
        }

        void Examine(AstNode node)
        {
            if (!workerJSParser.CancellationPending)
            {
                if (node.GetType() == typeof(FunctionObject))
                {
                    HandleAstNode(node as FunctionObject);
                }
                else if (node.GetType() == typeof(Lookup))
                {
                    HandleAstNode(node as Lookup);
                }
                else
                {
                    ; ; // Unhandle
                }
            }
            else
            {
                isCancelled = true;
            }
        }

        void Visit(AstNode node)
        {
            if (!workerJSParser.CancellationPending)
            {
                Examine(node);
                foreach (var child in node.Children)
                {
                    Visit(child);
                }
            }
            else
            {
                isCancelled = true;
            }
        }

        void workerJSParser_DoWork(object sender, DoWorkEventArgs e)
        {
            if (radioButtonWholeFile.Checked)
            {
                StreamReader inputReader = new StreamReader(inputFilePath);

                JSParser parser = new JSParser(inputReader.ReadToEnd());
                parser.CompilerError += new EventHandler<JScriptExceptionEventArgs>(parser_CompilerError);
                CodeSettings settings = new CodeSettings();
                Block block = parser.Parse(settings);

                Visit(block);
            }
            else if (radioButtonScriptTag.Checked)
            {
                HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
                doc.Load(inputFilePath);
                HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//script[not(@src)]");

                if (nodes == null || nodes.Count == 0)
                {
                    noScriptFound = true;
                    return;
                }

                foreach (HtmlNode node in nodes)
                    if (!workerJSParser.CancellationPending)
                    {
                        String strInnerText = node.InnerText;
                        scriptStartLineNumber = node.Line;

                        JSParser parser = new JSParser(strInnerText);
                        parser.CompilerError += new EventHandler<JScriptExceptionEventArgs>(parser_CompilerError);
                        CodeSettings settings = new CodeSettings();
                        Block block = parser.Parse(settings);

                        Visit(block);

                        if (isCancelled)
                        {
                            e.Cancel = true;
                            break;
                            //return;
                        }
                    }
                    else
                    {
                        isCancelled = true;
                        e.Cancel = true;
                        break;
                        //return;
                    }

            }
        }

        void workerJSParser_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            textBoxStatus.Text = "Locating...";
            textBoxStatus.Text += Environment.NewLine + "Located " + e.ProgressPercentage + " functions so far";
        }

        void workerJSParser_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            gridviewFunctionInfos.DataSource = dtblFunctionInfos;

            if (isCancelled)
            {
                textBoxStatus.Text = "Cancelled" + Environment.NewLine + "Located " + jsFunctionCount + " functions";
            }
            else if (noScriptFound)
            {
                textBoxStatus.Text = "Done" + Environment.NewLine + "No script is found";
            }
            else if (isParserError)
            {
                textBoxStatus.Text = "Done with Error";
                textBoxStatus.Text += Environment.NewLine + "Located " + jsFunctionCount + " functions"; ;

                gridviewError = new DataGridView();
                TabPage tabPageError = new TabPage();
                tabPageError.Name = "tabPageError";
                tabPageError.Text = "Error";
                tabControlResults.TabPages.Add(tabPageError);
                tabControlResults.TabPages["tabPageError"].Controls.Add(gridviewError);
                gridviewError.Dock = DockStyle.Fill;
            }
            else 
            {
                textBoxStatus.Text = "Done" + Environment.NewLine + "Located " + jsFunctionCount + " functions in total";
            }
            buttonParse.Enabled = true;
            buttonCancel.Enabled = false;
        }

        void parser_CompilerError(object sender, JScriptExceptionEventArgs ea)
        {
            isParserError = true;
            //parserErrorText = ea.Error.ToString();
            //if (workerJSParser.IsBusy)
            //{
            //    workerJSParser.CancelAsync();
            //}
            ; ;
        }

    }
}
